package ace.module.oauth2.server.core.impl.converter.impl;

import ace.module.oauth2.server.core.impl.converter.Oauth2ObjectJsonConverter;
import ace.module.oauth2.server.core.impl.converter.Oauth2RegisteredClientToRegisteredClientConverter;
import ace.module.oauth2.server.core.impl.dao.entity.Oauth2RegisteredClient;
import java.time.Instant;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import lombok.AllArgsConstructor;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.ConfigurationSettingNames;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
 * @author caspar
 * @date 2023/10/12 11:45
 */
@AllArgsConstructor
@Component
public class Oauth2RegisteredClientToRegisteredClientConverterImpl
    implements Oauth2RegisteredClientToRegisteredClientConverter {

  private final Oauth2ObjectJsonConverter oAuth2ObjectJsonConverter;

  @Override
  public RegisteredClient convert(Oauth2RegisteredClient source) {
    if (source == null) {
      return null;
    }
    Instant clientIdIssuedAt =
        Optional.ofNullable(source.getClientIdIssuedAt()).map(Instant::ofEpochMilli).orElse(null);
    Instant clientSecretExpiresAt =
        Optional.ofNullable(source.getClientSecretExpiresAt())
            .map(Instant::ofEpochMilli)
            .orElse(null);
    Set<String> clientAuthenticationMethods =
        StringUtils.commaDelimitedListToSet(source.getClientAuthenticationMethods());
    Set<String> authorizationGrantTypes =
        StringUtils.commaDelimitedListToSet(source.getAuthorizationGrantTypes());
    Set<String> redirectUris = StringUtils.commaDelimitedListToSet(source.getRedirectUris());
    Set<String> clientScopes = StringUtils.commaDelimitedListToSet(source.getScopes());

    RegisteredClient.Builder builder =
        RegisteredClient.withId(source.getId())
            .clientId(source.getClientId())
            .clientIdIssuedAt(clientIdIssuedAt)
            .clientSecret(source.getClientSecret())
            .clientSecretExpiresAt(clientSecretExpiresAt)
            .clientName(source.getClientName())
            .clientAuthenticationMethods(
                (authenticationMethods) ->
                    clientAuthenticationMethods.forEach(
                        authenticationMethod ->
                            authenticationMethods.add(
                                resolveClientAuthenticationMethod(authenticationMethod))))
            .authorizationGrantTypes(
                (grantTypes) ->
                    authorizationGrantTypes.forEach(
                        grantType -> grantTypes.add(resolveAuthorizationGrantType(grantType))))
            .redirectUris((uris) -> uris.addAll(redirectUris))
            .scopes((scopes) -> scopes.addAll(clientScopes));

    Map<String, Object> clientSettingsMap = parseMap(source.getClientSettings());
    builder.clientSettings(ClientSettings.withSettings(clientSettingsMap).build());

    Map<String, Object> tokenSettingsMap = parseMap(source.getTokenSettings());
    TokenSettings.Builder tokenSettingsBuilder = TokenSettings.withSettings(tokenSettingsMap);
    if (!tokenSettingsMap.containsKey(ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT)) {
      tokenSettingsBuilder.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED);
    }
    builder.tokenSettings(tokenSettingsBuilder.build());

    return builder.build();
  }

  private AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) {
    if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) {
      return AuthorizationGrantType.AUTHORIZATION_CODE;
    } else if (AuthorizationGrantType.CLIENT_CREDENTIALS
        .getValue()
        .equals(authorizationGrantType)) {
      return AuthorizationGrantType.CLIENT_CREDENTIALS;
    } else if (AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(authorizationGrantType)) {
      return AuthorizationGrantType.REFRESH_TOKEN;
    }
    return new AuthorizationGrantType(authorizationGrantType); // Custom authorization grant type
  }

  private ClientAuthenticationMethod resolveClientAuthenticationMethod(
      String clientAuthenticationMethod) {
    if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC
        .getValue()
        .equals(clientAuthenticationMethod)) {
      return ClientAuthenticationMethod.CLIENT_SECRET_BASIC;
    } else if (ClientAuthenticationMethod.CLIENT_SECRET_POST
        .getValue()
        .equals(clientAuthenticationMethod)) {
      return ClientAuthenticationMethod.CLIENT_SECRET_POST;
    } else if (ClientAuthenticationMethod.NONE.getValue().equals(clientAuthenticationMethod)) {
      return ClientAuthenticationMethod.NONE;
    }
    return new ClientAuthenticationMethod(
        clientAuthenticationMethod); // Custom client authentication method
  }

  private Map<String, Object> parseMap(String data) {
    return this.oAuth2ObjectJsonConverter.toMap(data);
  }
}
